﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using AtletIS.DomainModel.Entities.Main;
using AtletIS.Repository.UnitOfWork;
using AtletIS.Service.Base;
using AtletIS.Service.Enums;
using System.Collections.ObjectModel;

namespace AtletIS.Service.Services
{
    public class DisciplineListService : ServiceBase
    {

        #region Constructor

        public DisciplineListService()
        {
            this._unitOfWork = new UnitOfWork();
        }

        public DisciplineListService(IUnitOfWork unitOfWork)
        {
            this._unitOfWork = unitOfWork;
        }

        #endregion

        public DisciplineList GetById(int disciplineListId)
        {
            try
            {
                return _unitOfWork.Repository<DisciplineList>()
                        .Query(dl => dl.ID == disciplineListId)
                        .Include(i => i.Competition)
                        .Include(i => i.Category)
                        .Include(i => i.Discipline)
                        .Select()
                        .FirstOrDefault();
            }
            catch (Exception ex)
            {
                this.Error = 1;
                this.ErrorText = ex.Message;
                return null;
            }
        }

        public List<DisciplineList> GetByCompetitionId(int competitionId)
        {
            List<DisciplineList> mainList;

            try
            {
                // Level1
                mainList = _unitOfWork.Repository<DisciplineList>()
                        .Query(dl => dl.CompetitionId == competitionId && dl.ParentId == 0)
                        .Include(i => i.Competition)
                        .OrderBy(o => o.OrderBy(dl => dl.ExternalId))
                        .Select()
                        .ToList();

                if (mainList != null && mainList.Count > 0)
                {
                    foreach (DisciplineList mainItem in mainList)
                    {
                        // Level2
                        mainItem.SubDisciplineList = this._unitOfWork.Repository<DisciplineList>()
                            .Query(dl => dl.ParentId == mainItem.ID)
                            .Include(i => i.Competition)
                            .OrderBy(o => o.OrderBy(dl => dl.InnerCount))
                            .Select()
                            .ToList();

                        if (mainItem.SubDisciplineList != null && mainItem.SubDisciplineList.Count > 0)
                        {
                            foreach (DisciplineList subItem in mainItem.SubDisciplineList)
                            {
                                // Level3
                                subItem.SubDisciplineList = this._unitOfWork.Repository<DisciplineList>()
                                    .Query(dl => dl.ParentId == subItem.ID)
                                    .Include(i => i.Competition)
                                    .OrderBy(o => o.OrderBy(dl => dl.InnerCount))
                                    .Select()
                                    .ToList();
                            }
                        }
                    }
                }

                return mainList;
            }
            catch (Exception ex)
            {
                this.Error = 1;
                this.ErrorText = ex.Message;
                return null;
            }
        }

        internal List<DisciplineList> GetByParentId(int parentId)
        {
            try
            {
                return _unitOfWork.Repository<DisciplineList>()
                        .Query(dl => dl.ParentId == parentId)
                        .Include(i => i.Competition)
                        .OrderBy(o => o.OrderBy(dl => dl.InnerCount))
                        .Select()
                        .ToList();
            }
            catch (Exception ex)
            {
                this.Error = 1;
                this.ErrorText = ex.Message;
                return null;
            }
        }

        #region Save

        /// <summary>
        ///  Uloží disciplínu podľa stavu
        /// </summary>
        public bool Save(DisciplineList disciplineList)
        {
            try
            {
                switch (disciplineList.ObjectState)
                { 
                    case DomainModel.ObjectState.ObjectState.Added:
                        this._unitOfWork.Repository<DisciplineList>().Insert(disciplineList);
                        break;

                    case DomainModel.ObjectState.ObjectState.Modified:
                        this._unitOfWork.Repository<DisciplineList>().Update(disciplineList);
                        break;

                    case DomainModel.ObjectState.ObjectState.Deleted:
                        this._unitOfWork.Repository<DisciplineList>().Delete(disciplineList);
                        break;

                    default:
                        return true;
                }

                this._unitOfWork.SaveChanges();

                return true;
            }
            catch (Exception ex)
            {
                this.Error = 1;
                this.ErrorText = ex.Message;
                return false;
            }
        }

        #endregion

        #region Split/Advance

        /// <summary>
        ///  Vytvorí novú disciplínu v nadradenej disciplíne
        /// </summary>
        public DisciplineList CreateSubDisciplineList(DisciplineList parentDisciplineList, string name)
        {
            int innerCount = 1;
            DisciplineList disciplineList;
            List<DisciplineList> subDisciplineList;

            try
            {
                this.Error = 0;

                subDisciplineList = this.GetByParentId(parentDisciplineList.ID);
                if (subDisciplineList != null && subDisciplineList.Count > 0)
                {
                    innerCount = subDisciplineList.OrderByDescending(dl => dl.InnerCount).First().InnerCount.Value;
                }

                disciplineList = new DisciplineList()
                {
                    Name = name,
                    Description = string.Empty,
                    ExternalId = parentDisciplineList.ExternalId,
                    ParentId = parentDisciplineList.ID,
                    CompetitionId = parentDisciplineList.CompetitionId,
                    DisciplineId = parentDisciplineList.DisciplineId,
                    CategoryId = parentDisciplineList.CategoryId,
                    Day = parentDisciplineList.Day,
                    OfficialStartTime = parentDisciplineList.OfficialStartTime,
                    StartTime = parentDisciplineList.StartTime,
                    Status = parentDisciplineList.Status,
                    DisciplineCode = parentDisciplineList.DisciplineCode,
                    Note = string.Empty,
                    InnerCount = innerCount,
                    MeasurementId = 1,
                    Relay = parentDisciplineList.Relay,
                    Active = true,
                    Sync = false,
                    WebId = 0,
                    Modified = DateTime.Now,
                    ObjectState = DomainModel.ObjectState.ObjectState.Added
                };

                // Save new sub discipline
                this._unitOfWork.Repository<DisciplineList>().Insert(disciplineList);
                this._unitOfWork.SaveChanges();

                return disciplineList;
            }
            catch (Exception ex)
            {
                this.Error = 1;
                this.ErrorText = ex.Message;
                return null;
            }
        }

        /// <summary>
        ///  Vytvorí rozdelenie disciplíny
        /// </summary>
        public bool Split(DisciplineList disciplineList, string header, DisciplineListSplitEnum splitType, int numberOfHeats = 0)
        {
            Competition competition;
            DisciplineList splitHeader;
            List<DisciplineList> splitSubDisciplineList = new List<DisciplineList>();
            List<Performance> performances;

            try
            {
                this.Error = 0;

                competition = new CompetitionService(this._unitOfWork).GetById(disciplineList.CompetitionId.Value);
                performances = new PerformancesService(this._unitOfWork).GetByDisciplineListId(disciplineList.ID, PerformancesSortEnum.StartListSbPb);

                #region Number of heats

                if (numberOfHeats == 0)
                {
                    numberOfHeats = performances.Count / competition.Lanes.Value;
                    if (performances.Count % competition.Lanes.Value > 0)
                    {
                        numberOfHeats += 1;
                    }
                }

                #endregion

                #region Split header

                splitHeader = new DisciplineList()
                {
                    Name = header,
                    Description = string.Empty,
                    ExternalId = disciplineList.ExternalId,
                    ParentId = disciplineList.ID,
                    CompetitionId = disciplineList.CompetitionId,
                    DisciplineId = disciplineList.DisciplineId,
                    CategoryId = disciplineList.CategoryId,
                    Day = disciplineList.Day,
                    OfficialStartTime = disciplineList.OfficialStartTime,
                    StartTime = disciplineList.StartTime,
                    Status = disciplineList.Status,
                    DisciplineCode = disciplineList.DisciplineCode,
                    Note = string.Empty,
                    InnerCount = 1,
                    MeasurementId = 1,
                    Relay = disciplineList.Relay,
                    Active = true,
                    Sync = false,
                    WebId = 0,
                    Modified = DateTime.Now,
                    ObjectState = DomainModel.ObjectState.ObjectState.Added
                };

                // Save split header
                this._unitOfWork.Repository<DisciplineList>().Insert(splitHeader);
                this._unitOfWork.SaveChanges();

                #endregion

                #region Split disciplines

                for (int i = 1; i <= numberOfHeats; i++)
                {
                    DisciplineList subdiscipline = new DisciplineList()
                    {
                        Name = string.Format("{0} {1}", header, i),
                        Description = string.Empty,
                        ExternalId = disciplineList.ExternalId,
                        ParentId = splitHeader.ID,
                        CompetitionId = splitHeader.CompetitionId,
                        DisciplineId = splitHeader.DisciplineId,
                        CategoryId = splitHeader.CategoryId,
                        Day = splitHeader.Day,
                        OfficialStartTime = splitHeader.OfficialStartTime,
                        StartTime = splitHeader.StartTime,
                        Status = splitHeader.Status,
                        DisciplineCode = splitHeader.DisciplineCode,
                        Note = string.Empty,
                        InnerCount = i,
                        MeasurementId = 1,
                        Relay = splitHeader.Relay,
                        Active = true,
                        Sync = false,
                        WebId = 0,
                        Modified = DateTime.Now,
                        ObjectState = DomainModel.ObjectState.ObjectState.Added
                    };

                    splitSubDisciplineList.Add(subdiscipline);
                }

                // Save subdisciplines
                this._unitOfWork.Repository<DisciplineList>().InsertRange(splitSubDisciplineList);
                this._unitOfWork.SaveChanges();

                #endregion

                #region Split performances

                /* NAJLEPŠÍ SPOLU 
                 * Zoradení pretekári sa priraďujú postupne do disciplín, dráhy sa priraďujú podľa výhodnosti.
                 * Najlepší pretekári do prvej disciplíny. */
                if (splitType == DisciplineListSplitEnum.BestTogether)
                {
                    int splitted = 0;
                    foreach (DisciplineList dl in splitSubDisciplineList)
                    {
                        for (int i = 0; i < competition.Lanes; i++)
                        {
                            if (splitted + i >= performances.Count)
                                break;

                            performances[splitted + i].DisciplineListId = dl.ID;
                            performances[splitted + i].Lane = Convert.ToInt32(competition.aLanesQuality[i]);

                            // Set to update
                            _unitOfWork.Repository<Performance>().Update(performances[splitted + i]);
                        }

                        splitted += competition.Lanes.Value;
                    }
                }

                /* NAJLEPŠÍ ODDELENE
                 * Zoradeným pretekárom sa postupne priraďujú dráhy, najskôr najvýhodnejšia vo všetkých disciplínach.
                 * Najlepší pretekári do najvýhodnejšej dráhy. */
                if (splitType == DisciplineListSplitEnum.BestSeparated)
                {
                    int splitted = 0;
                    for (int i = 0; i < competition.Lanes.Value; i++)
                    {
                        for (int j = 0; j < splitSubDisciplineList.Count; j++)
                        {
                            if (splitted + j >= performances.Count)
                                break;

                            performances[splitted + j].DisciplineListId = splitSubDisciplineList[j].ID;
                            performances[splitted + j].Lane = Convert.ToInt32(competition.aLanesQuality[i]);

                            // Set to update
                            _unitOfWork.Repository<Performance>().Update(performances[splitted + j]);
                        }

                        splitted += splitSubDisciplineList.Count;
                    }
                }

                // Save performances
                this._unitOfWork.SaveChanges();

                #endregion

                return true;
            }
            catch (Exception ex)
            {
                this.Error = 1;
                this.ErrorText = ex.Message;
                return false;
            }
        }

        /// <summary>
        ///  Vytvorí postupové kolo
        /// </summary>
        public bool Advance(DisciplineList fromDisciplineList, string header, DisciplineListAdvanceTypeEnum advanceType, DisciplineListAdvanceKeyEnum advanceKey, int takeFromHeat, int numberOfHeats = 0)
        {
            int totalPerformances;
            Competition competition;
            DisciplineList advanceHeader;
            List<DisciplineList> advanceSubDisciplineList = new List<DisciplineList>();
            List<Performance> performances;

            try
            {
                this.Error = 0;
                
                competition = new CompetitionService(this._unitOfWork).GetById(fromDisciplineList.CompetitionId.Value);
                performances = new PerformancesService(this._unitOfWork).GetByParentDisciplineListId(fromDisciplineList.ID, PerformancesSortEnum.Results);

                #region Number of heats

                if (numberOfHeats == 0)
                {
                    numberOfHeats = performances.Count / competition.Lanes.Value;
                    if (performances.Count % competition.Lanes.Value > 0)
                    {
                        numberOfHeats += 1;
                    }
                }

                #endregion

                #region Performances

                totalPerformances = numberOfHeats * competition.Lanes.Value;

                // Get performances for Advance key
                switch (advanceKey)
                { 
                    case DisciplineListAdvanceKeyEnum.BestInTotal:
                        performances = performances.Take(totalPerformances).ToList();
                        break;

                    case DisciplineListAdvanceKeyEnum.BestInHeatThenBestInTotal:
                        performances = new PerformancesService(this._unitOfWork).GetByParentDisciplineListIdBestInHeatThenBestInTotal(fromDisciplineList.ID, takeFromHeat, totalPerformances, PerformancesSortEnum.Results);
                        break;

                    default:
                        this.Error = -1;
                        this.ErrorText = "Neznámy postupový kľúč.";
                        return false;
                }

                #endregion

                #region Advance header

                advanceHeader = new DisciplineList()
                {
                    Name = header,
                    Description = string.Empty,
                    ExternalId = fromDisciplineList.ExternalId,
                    ParentId = fromDisciplineList.ParentId,
                    CompetitionId = fromDisciplineList.CompetitionId,
                    DisciplineId = fromDisciplineList.DisciplineId,
                    CategoryId = fromDisciplineList.CategoryId,
                    Day = fromDisciplineList.Day,
                    OfficialStartTime = fromDisciplineList.OfficialStartTime,
                    StartTime = fromDisciplineList.StartTime,
                    Status = fromDisciplineList.Status,
                    DisciplineCode = fromDisciplineList.DisciplineCode,
                    Note = string.Empty,
                    InnerCount = fromDisciplineList.InnerCount + 1,
                    MeasurementId = 1,
                    Relay = fromDisciplineList.Relay,
                    Active = true,
                    Sync = false,
                    WebId = 0,
                    Modified = DateTime.Now,
                    ObjectState = DomainModel.ObjectState.ObjectState.Added
                };

                // Save split header
                this._unitOfWork.Repository<DisciplineList>().Insert(advanceHeader);
                this._unitOfWork.SaveChanges();

                #endregion

                #region Advance disciplines

                for (int i = 1; i <= numberOfHeats; i++)
                {
                    DisciplineList subdiscipline = new DisciplineList()
                    {
                        Name = string.Format("{0} {1}", header, i),
                        Description = string.Empty,
                        ExternalId = fromDisciplineList.ExternalId,
                        ParentId = advanceHeader.ID,
                        CompetitionId = advanceHeader.CompetitionId,
                        DisciplineId = advanceHeader.DisciplineId,
                        CategoryId = advanceHeader.CategoryId,
                        Day = advanceHeader.Day,
                        OfficialStartTime = advanceHeader.OfficialStartTime,
                        StartTime = advanceHeader.StartTime,
                        Status = advanceHeader.Status,
                        DisciplineCode = advanceHeader.DisciplineCode,
                        Note = string.Empty,
                        InnerCount = i,
                        MeasurementId = 1,
                        Relay = advanceHeader.Relay,
                        Active = true,
                        Sync = false,
                        WebId = 0,
                        Modified = DateTime.Now,
                        ObjectState = DomainModel.ObjectState.ObjectState.Added
                    };

                    advanceSubDisciplineList.Add(subdiscipline);
                }

                // Save subdisciplines
                this._unitOfWork.Repository<DisciplineList>().InsertRange(advanceSubDisciplineList);
                this._unitOfWork.SaveChanges();

                #endregion

                #region Split performances

                /* NAJLEPŠÍ SPOLU 
                 * Zoradení pretekári sa priraďujú postupne do disciplín, dráhy sa priraďujú podľa výhodnosti.
                 * Najlepší pretekári do prvej disciplíny. */
                if (advanceType == DisciplineListAdvanceTypeEnum.BestTogether)
                {
                    int splitted = 0;
                    foreach (DisciplineList dl in advanceSubDisciplineList)
                    {
                        for (int i = 0; i < competition.Lanes; i++)
                        {
                            if (splitted + i >= performances.Count)
                                break;

                            performances[splitted + i].DisciplineListId = dl.ID;
                            performances[splitted + i].Lane = Convert.ToInt32(competition.aLanesQuality[i]);
                            performances[splitted + i].Rank = 0;
                            performances[splitted + i].AdvResult = performances[splitted + i].Result;
                            performances[splitted + i].Result = 0;
                            performances[splitted + i].Reaction = string.Empty;

                            // Set to update
                            this._unitOfWork.Repository<Performance>().Insert(performances[splitted + i]);
                        }

                        splitted += competition.Lanes.Value;
                    }
                }

                /* NAJLEPŠÍ ODDELENE
                 * Zoradeným pretekárom sa postupne priraďujú dráhy, najskôr najvýhodnejšia vo všetkých disciplínach.
                 * Najlepší pretekári do najvýhodnejšej dráhy. */
                if (advanceType == DisciplineListAdvanceTypeEnum.BestSeparated)
                {
                    int splitted = 0;
                    for (int i = 0; i < competition.Lanes.Value; i++)
                    {
                        for (int j = 0; j < advanceSubDisciplineList.Count; j++)
                        {
                            if (splitted + j >= performances.Count)
                                break;

                            performances[splitted + j].DisciplineListId = advanceSubDisciplineList[j].ID;
                            performances[splitted + j].Lane = Convert.ToInt32(competition.aLanesQuality[i]);
                            performances[splitted + j].Rank = 0;
                            performances[splitted + j].AdvResult = performances[splitted + j].Result;
                            performances[splitted + j].Result = 0;
                            performances[splitted + j].Reaction = string.Empty;

                            // Set to update
                            this._unitOfWork.Repository<Performance>().Insert(performances[splitted + j]);
                        }

                        splitted += advanceSubDisciplineList.Count;
                    }
                }

                // Save performances
                this._unitOfWork.SaveChanges();

                #endregion

                return true;
            }
            catch (Exception ex)
            {
                this.Error = 1;
                this.ErrorText = ex.Message;
                return false;
            }
        }

        /// <summary>
        ///  Zruší disciplínu (rozdelenie disciplíny alebo postupové kolo)
        /// </summary>
        public bool Cancel(DisciplineList disciplineListToCancel)
        {
            List<DisciplineList> subDisciplineList;
            List<Performance> performances;

            try
            {
                this.Error = 0;

                #region Cancel subDisciplineList

                subDisciplineList = this.GetByParentId(disciplineListToCancel.ID);

                if (subDisciplineList != null && subDisciplineList.Count > 0)
                {
                    for (int i = subDisciplineList.Count - 1; i >= 0; i--)
                    {
                        performances = new PerformancesService(this._unitOfWork).GetByDisciplineListId(subDisciplineList[i].ID, PerformancesSortEnum.StartList);

                        if (performances != null && performances.Count > 0)
                        {

                            foreach (Performance perf in performances)
                            {
                                // Split
                                if (disciplineListToCancel.InnerCount == 1)
                                {
                                    perf.DisciplineListId = disciplineListToCancel.ParentId;
                                    perf.Lane = 0;
                                    this._unitOfWork.Repository<Performance>().Update(perf);
                                }

                                // Advance
                                if (disciplineListToCancel.InnerCount > 1)
                                {
                                    this._unitOfWork.Repository<Performance>().Delete(perf);
                                }
                            }
                        }

                        this._unitOfWork.Repository<DisciplineList>().Delete(subDisciplineList[i]);
                    }
                }

                #endregion

                this._unitOfWork.Repository<DisciplineList>().Delete(disciplineListToCancel);
                this._unitOfWork.SaveChanges();

                return true;
            }
            catch (Exception ex)
            {
                this.Error = 1;
                this.ErrorText = ex.Message;
                return false;
            }
        }

        #endregion

    }
}
